﻿/**
 * This software is released under the MIT License
 * 
 * Copyright (c) 2009 Philippe Van Kessel
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.calefk.util
{
	import flash.display.IBitmapDrawable;
	import flash.display.Sprite;
	
	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Matrix;
	
	public class DistortGrid 
	{
		// Reference to the BitmapData capture of the source MovieClip.
		// You should never change the value of this property.
		public var _source:BitmapData;
		
		// Reference to the MovieClip containing the distorted image.
		// If you change it, future calls to the render method will render to this MovieClip.
		public var _container:Sprite;
		
		// Points array. Organized like this _pp[row][column].
		// After you change points, call the render method for the changes to take effect.
		public var _pp:Array;
		
		// Number of columns (horizontal precision), number of rows (vertical position). Should never be set.
		// You should never change the value of these properties.
		public var _nc:int, _nr:int;
		
		// Width and height of the source MovieClip or BitmapData.
		// You should never change the value of these properties.
		protected var _w:Number, _h:Number;
		
		// Normal width and height of the triangles (when there is no distorsion).
		// You should never change the value of these properties.
		protected var _lx:Number, _ly:Number;
		
		// Controls whether to apply smoothing or not.
		// Call the render method after changing the value of this property for the change to take effect.
		public var _smoothing:Boolean;
		
		// Controls the line style of the triangles.
		// Call the render method after changing the value of those properties for the changes to take effect.
		public var _lineThickness:Number, _lineColor:uint, _lineAlpha:Number; // visual options (smoothing, line style)
		
		
		
		// ---------------------------------------------------------------------------------------------------------
		//	Initialization
		// ---------------------------------------------------------------------------------------------------------
		
		/**
		 * Constructor.
		 * 
		 * @param	$container	The MovieClip which will contain the distorted Bit
		 * @param	$source		The BitmapData, Sprite or Shape to distort
		 * @param	$nc			The number of columns (horizontal precision). Default value is 1
		 * @param	$nr			The number of rows (vertical precision). Default value is 1
		 */
		public function DistortGrid($container:Sprite, $source:*, $nc:int, $nr:int)
		{
			if ($source is BitmapData)
			{
				_source = $source;
			}
			else
			{
				_source = new BitmapData($source.width, $source.height, true);
				_source.draw($source);
			}
			
			_container = $container;
			
			_pp = [];
			_nc = (isNaN($nc) || $nc < 1) ? 1 : $nc;
			_nr = (isNaN($nr) || $nr < 1) ? 1 : $nr;
			
			_w = $source.width;
			_h = $source.height;
			_lx = _w / _nc;
			_ly = _h / _nr;
			
			_smoothing = false;
			_lineThickness = 0;
			_lineColor = 0x000000;
			_lineAlpha = 0;
			
			// -- Create points --------------------------------------------------
			//
				var ir:int, ic:int, x:Number, y:Number;
				
				for (ir = 0 ; ir < _nr + 1 ; ir++)
				{
					y = _h / _nr * ir
					_pp[ir] = [];
					
					for (ic = 0 ; ic < _nc + 1 ; ic++)
					{
						x = _w / _nc * ic;
						_pp[ir][ic] = new Point(x, y);
					}
				}
			//
			// -------------------------------------------------------------------
			
			render();
		}
		
		/**
		 * Clears every property to make them elibigle for garbage collection.
		 */
		public function clear():void
		{
			var ir:int, ic:int;
			
			for (ir = 0 ; ir < _nr + 1 ; ir++)
			{
				for (ic = 0 ; ic < _nc + 1 ; ic++)
					_pp[ir][ic] = null;
				
				_pp[ir] = null;
			}
			
			_pp = null;
			_nc = 0;
			_nr = 0;
			
			_w = 0;
			_h = 0;
			_lx = 0;
			_ly = 0;
			
			_smoothing = false;
			_lineThickness = 0;
			_lineColor = 0;
			_lineAlpha = 0;
			
			_container = null;
			_source = null;
		}
		
		
		
		// ---------------------------------------------------------------------------------------------------------
		//	Public methods
		// ---------------------------------------------------------------------------------------------------------
		
		/**
		 * Changes the distorted BitmapData or MovieClip
		 * 
		 * @param	$source	The BitmapData or the MovieClip to distort
		 */
		public function setSource($source):void
		{
			if ($source is BitmapData)
			{
				_source = $source;
			}
			else
			{
				_source = new BitmapData($source.width, $source.height, true);
				_source.draw($source);
			}
			
			_w = _source.width;
			_h = _source.height;
			_lx = _w / _nc;
			_ly = _h / _nr;
			
			render();
		}
		
		/**
		 * Renders the distorted image. Must be called for any change to take effect (NOT automatically called)
		 */
		public function render():void
		{
			var ir:int, ic:int, tx1:Number, ty1:Number, m:Matrix = new Matrix();
			var p1:Point, p2:Point, p3:Point, p4:Point;
			
			_container.graphics.clear();
			_container.graphics.lineStyle(_lineThickness, _lineColor, _lineAlpha);
			
			for (ir = 0 ; ir < _nr ; ir++)
			{
				ty1 = -_ly * ir;
				
				for (ic = 0 ; ic < _nc ; ic++)
				{
					p1 = _pp[ir][ic];
					p2 = _pp[ir][ic + 1];
					p3 = _pp[ir + 1][ic];
					p4 = _pp[ir + 1][ic + 1];
					
					tx1 = -_lx * ic;
					
					m.tx = p1.x;
					m.ty = p1.y;
					m.a = (p2.x - m.tx) / _lx;
					m.b = (p2.y - m.ty) / _lx;
					m.c = (p3.x - m.tx) / _ly;
					m.d = (p3.y - m.ty) / _ly;
					
					// Concat shortcut
					m.tx += tx1 * m.a + ty1 * m.c;
					m.ty += tx1 * m.b + ty1 * m.d;
					
					_container.graphics.beginBitmapFill(_source, m, false, _smoothing);
					_container.graphics.moveTo(p1.x, p1.y);
					_container.graphics.lineTo(p2.x, p2.y);
					_container.graphics.lineTo(p3.x, p3.y);
					_container.graphics.endFill();
					
					m.tx = p3.x + (p2.x - p4.x);
					m.ty = p3.y + (p2.y - p4.y);
					m.a = (p2.x - m.tx) / _lx;
					m.b = (p2.y - m.ty) / _lx;
					m.c = (p3.x - m.tx) / _ly;
					m.d = (p3.y - m.ty) / _ly;
					
					// Concat shortcut
					m.tx += tx1 * m.a + ty1 * m.c;
					m.ty += tx1 * m.b + ty1 * m.d;
					
					_container.graphics.beginBitmapFill(_source, m, false, _smoothing);
					_container.graphics.moveTo(p2.x, p2.y);
					_container.graphics.lineTo(p3.x, p3.y);
					_container.graphics.lineTo(p4.x, p4.y);
					_container.graphics.endFill();
				}
			}
		}
	}
}